;
(function (w, doc, Watcher, Watch, Computed, Dep) {

    const subscriber = new Dep()
    class Vue {
        constructor(options) {
            const { el, data, watch, computed, methods } = options;
            this.$el = doc.querySelector(el);
            this.$data = typeof data === 'function' ? data.call(this) : data;
            this.methods = methods
            this.eventPool = [];
            this.showPool = [];
            this.init(this, watch, computed)
        }
        init(vm, watch, computed) {
            //   监听
            this.observe(vm.$data);
            //  代理到vm实力上
            this.initProxy(vm, (key, newVal, oldVal) => {
                if (newVal === oldVal) {
                    return
                }
                this.changeShow(vm.showPool, key)
                this.$computed(key, () => {
                    subscriber.notify()
                })
                this.$watch(key, newVal, oldVal)
            })
            this.initComputed(vm, computed);
            this.initWatch(vm, watch);
            this.initView(vm.$el);
            this.changeShow(vm.showPool, false)
            this.initEvent(vm.eventPool)
        }
        observe(data) {
            Object.keys(data).forEach(k => {
                this.defindReactive(data, k);
            })
        }
        defindReactive(data, k) {
            let value = data[k];
            if (typeof value === 'object' && value) {
                this.observe(value);
                return
            }
            Object.defineProperty(data, k, {
                enumerable: true,
                configurable: true,
                get() {
                    Dep.target && subscriber.addSub(Dep.target);
                    return value
                },
                set(newVal) {
                    value = newVal
                    subscriber.notify()
                }
            })

        }
        initProxy(vm, fn) {
            const data = vm.$data;
            Object.keys(data).forEach(k => {
                Object.defineProperty(vm, k, {
                    enumerable: true,
                    configurable: true,
                    get() {
                        return vm.$data[k]
                    },
                    set(val) {
                        const oldVal = vm.$data[k]
                        console.log('set');
                        vm.$data[k] = val;
                        fn(k, val, oldVal)
                    }
                })
            })
        }
        initComputed(vm, computed) {
            const computedIns = new Computed();
            for (let k in computed) {
                computedIns.addComputed(vm, computed, k, subscriber);
            }
            this.$computed = computedIns.update.bind(computedIns)
        }
        initWatch(vm, watch) {
            const watchIns = new Watch()
            for (let k in watch) {
                watchIns.addWatch(vm, watch, k)
            }
            this.$watch = watchIns.invoke.bind(watchIns);
        }
        initView(node) {
            if (node.nodeType === 1 && node.tagName === 'INPUT') {
                const key = node.getAttribute('v-model');
                if (key) {
                    const keyArr = key.split('.');
                    const value = keyArr.reduce((obj, k) => obj[k], this);
                    node.value = value;
                    new Watcher(this, key, (newVal) => {
                        node.value = newVal
                    })
                    node.addEventListener('input', e => {
                        e = e || w.event;
                        const lastKey = keyArr[keyArr.length - 1];
                        const lastObj = keyArr.slice(0, keyArr.length - 1).reduce((obj, k) => obj[k], this);
                        lastObj[lastKey] = e.target.value;
                    }, false)
                }
            } else if (node.nodeType === 1) {
                const event = node.getAttribute('@click');
                const vif = node.getAttribute('v-if');
                const vshow = node.getAttribute('v-show');
                if (event) {

                    this.eventPool.push({
                        dom: node,
                        type: 'click',
                        fn: this.methods[event]
                    })
                }
                if (vif) {
                    this.showPool.push({
                        show: this.$data[vif],
                        key: vif,
                        type: 'vif',
                        dom: node,
                    })
                } else if (vshow) {
                    this.showPool.push({
                        dom: node,
                        show: this.$data[vshow],
                        key: vshow,
                        type: 'vshow'
                    })
                }

            } else if (node.nodeType === 3) {
                const reg = /\{\{\s*(\S+)\s*}\}/g;
                const text = node.textContent;
                const execRes = reg.exec(text);
                if (execRes) {
                    const key = execRes[1];

                    const value = key.split('.').reduce((obj, k) => obj[k], this);
                    node.textContent = text.replace(reg, value);
                    new Watcher(this, key, newVal => {
                        node.textContent = text.replace(reg, newVal);
                    })
                }
                return
            }

            const childNodes = node.childNodes;
            if (!childNodes.length) { return };
            childNodes.forEach(dom => this.initView(dom))
        }
        initEvent(eventPool) {
            for (let v of eventPool) {
                v.dom.addEventListener(v.type, v.fn.bind(this), false)
            }
        }
        changeShow(showPool, key) {
            if (!key) {
                for (let v of showPool) {

                    switch (v.type) {
                        case 'vif':
                            v.comment = doc.createComment('v-f');
                            !v.show && v.dom.parentNode.replaceChild(v.comment, v.dom);
                            break;
                        case 'vshow':

                            v.show ? '' : v.dom.style.display = 'none';
                            break;
                        default:
                            break;

                    }
                }
                return
            }
            for (let v of showPool) {

                if (key === v.key) {
                    console.log(v);
                    switch (v.type) {
                        case 'vif':
                            v.show = !v.show;
                            v.show ? v.comment.parentNode.replaceChild(v.dom, v.comment) :
                                v.dom.parentNode.replaceChild(v.comment, v.dom);
                            break;
                        case 'vshow':
                            v.show = !v.show;
                            v.show ? v.dom.style.display = 'block' : v.dom.style.display = 'none';
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }
    w.Vue = Vue;
})(window, document, Watcher, Watch, Computed, Dep)